import 'package:flutter/foundation.dart' show ValueListenable, VoidCallback;
import 'package:yeild_libs_dal/src/scope.dart';

import 'repository.dart';

/// Callback info for repository changes.
class RepoChangedCallbackInfo {
  /// The type of the repository that this callback is for.
  Type repoType;

  /// The callback function that will be called when the repository changes.
  DalRepoChangedCallback callback;

  RepoChangedCallbackInfo(this.repoType, this.callback);
}

/// A listener info for value changes in a [ValueListenable].
class ValueListenerInfo {
  /// The [ValueListenable] that this listener is attached to.
  ValueListenable listenable;

  /// The callback function that will be called when the value changes.
  VoidCallback callback;
  dynamic userCallback;

  ValueListenerInfo(this.listenable, this.userCallback, this.callback);
}

/// A mixin that provides methods to automatically watch repositories and value listeners,
/// and ensures that they are disposed of when the class is disposed.
/// This mixin is typically used in view models or other classes that need to
/// manage resources in a DAL context.
mixin DalDisposable {
  /// The DAL scope for this mixin.
  DalScope get scope;

  /// A list of registered repository change callbacks.
  final List<RepoChangedCallbackInfo> _repoChangedCallbacks = [];

  /// A list of registered value listeners.
  final List<ValueListenerInfo> _valueListeners = [];

  /// Get a [Scope] from the DAL scope.
  Scope? dalScope<Scope extends DalScope>() => scope.scope<Scope>();

  /// Get a [Repo] from the DAL scope.
  Repo? dalRepo<Repo extends DalRepository>() => scope.repo<Repo>();

  /// Disposes of all registered repository change callbacks and value listeners.
  void dalDispose() {
    // Dispose all registered repository change callbacks
    for (var repoChangedCallback in _repoChangedCallbacks) {
      scope.unwatchRepoByType(
        repoChangedCallback.callback,
        repoChangedCallback.repoType,
      );
      // Call the callback with null to indicate the repository is no longer available
      repoChangedCallback.callback(null);
    }
    _repoChangedCallbacks.clear();
    // Dispose all registered value listeners
    for (var valueListener in _valueListeners) {
      valueListener.listenable.removeListener(valueListener.callback);
    }
    _valueListeners.clear();
  }

  /// Watches for changes in a specific repository type and calls the provided [callback]
  /// The repository is automatically removed when the view model is disposed.
  void autoWatchRepo<Repo extends DalRepository>(
    void Function(Repo? repo) callback,
  ) {
    RepoChangedCallbackInfo repoChangedCallback = RepoChangedCallbackInfo(
      Repo,
      (repo) async {
        callback(repo as Repo?);
      },
    );
    if (scope.watchRepo<Repo>(repoChangedCallback.callback)) {
      _repoChangedCallbacks.add(repoChangedCallback);
    }
  }

  /// Watches for changes in a [ValueListenable] and calls the provided [callback].
  /// If the [ValueListenable] is null, it calls the callback with null.
  /// The [ValueListenable] is automatically removed when the view model is disposed.
  void autoWatchValue<T>(
    ValueListenable<T>? listenable,
    void Function(T? value) callback,
  ) {
    if (listenable == null) {
      // Call with null if the ValueListenable is null
      callback(null);
      // Remove the listener if it already exists
      _valueListeners.removeWhere((listener) {
        if (callback == listener.userCallback) {
          listener.listenable.removeListener(listener.callback);
          return true;
        }
        return false;
      });
      return;
    }
    if (_valueListeners.any(
      (listener) =>
          callback == listener.userCallback &&
          identical(listener.listenable, listenable),
    )) {
      // If the listener already exists, just do nothing
      return;
    }
    ValueListenerInfo valueListener = ValueListenerInfo(
      listenable,
      callback,
      () {
        callback(listenable.value);
      },
    );
    _valueListeners.add(valueListener);
    // Call the callback immediately with the current value
    callback(listenable.value);
    listenable.addListener(valueListener.callback);
  }
}
